﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Cofe.Core.Property
{
    public class MemoryPropertyCache : PropertyProviderBase, IPropertyCache
    {


        #region Constructor

        [Export(typeof(ICofeService))]
        [ServicePriority(ServicePriorityAttribute.DefaultPriority_COFE)]
        public class Factory : IPropertyCacheFactory
        {
            static Dictionary<string, MemoryPropertyCache> _memoryCacheDic =
                new Dictionary<string, MemoryPropertyCache>();

            public virtual IPropertyCache CreatePropertyCache(ParsableType pathType, string path)
            {
                if (pathType != ParsableType.ParsePath)
                    throw new NotSupportedException();

                var newCache = new MemoryPropertyCache();
                newCache.SetPropertyAsync(CofeProperties.FullParseName, path).Wait();
                return newCache;

                //lock (_memoryCacheDic)
                //{
                //    if (!(_memoryCacheDic.ContainsKey(path)))
                //    {
                //        var newCache = new MemoryPropertyCache();
                //        newCache.SetPropertyAsync(CofeProperties.FullParseName, path).Wait();
                //        _memoryCacheDic.Add(path, newCache);
                //    }

                //    return _memoryCacheDic[path];
                //}
            }

            public virtual IPropertyCache CreatePropertyCache()
            {
                return new MemoryPropertyCache();
            }

            public virtual IPropertyCache CreatePropertyCache(IPropertyCache[] propertyCaches)
            {
                ConcurrentDictionary<object, object> retDic = new ConcurrentDictionary<object,object>();

                foreach (var pc in propertyCaches)
                    if (pc is MemoryPropertyCache)
                    {
                        var pcDic = (pc as MemoryPropertyCache)._propertyValueDic;
                        foreach (var prop in pcDic.Keys)
                            if (!(retDic.ContainsKey(prop)))
                                retDic[prop] = pcDic[prop];
                    }
                    else
                    {
                        foreach (var prop in pc.SupportedDefinitions.GetSupportedProperties())
                            if (pc.IsPropertySupported(prop) && !retDic.ContainsKey(prop))
                                retDic[prop] = pc.GetOneProperty(prop);
                    }

                var retVal = new MemoryPropertyCache { _propertyValueDic = retDic };
                return retVal;
            }

            public virtual ParsableType SupportedParsableType
            {
                get { return ParsableType.ParsePath; }
            }
        }

        public MemoryPropertyCache()
        {
            registerProperties(CacheProperties.SupportedProperties, () => _supportedProperties.ToArray(),
                                (ppair) =>
                                {
                                    _supportedProperties = new List<object>(ppair.ValueAs<object[]>()); return true;
                                });

            registerProperties(CacheProperties.LastCachedTimeUtc, () => _lastUpdateTime,
                               (ppair) => { _lastUpdateTime = ppair.ValueAs<DateTime>(); return true; });

            registerProperties(CacheProperties.CachedProperties, () => _propertyValueDic.Keys.ToArray());

            registerActionAsync(CofeProperties.RefreshAsync, (pm) =>  CofeServices.TaskManager.TaskConstructor
                .CreateTask(null, CofeProperties.RefreshAsync, (prop) => { this.ResetCache(); } ));            

            registerProperties(CacheProperties.Id, () => _entryId, (ppair) => { _entryId = ppair.ValueAs<Guid>(); return true; });
        }

        #endregion


        #region Methods

        protected override List<object> getSupportedProperties()
        {
            var retVal = base.getSupportedProperties();
            if (_supportedProperties == null || _supportedProperties.Count() == 0)
            {
                retVal.Remove(CacheProperties.SupportedProperties);
                return retVal;
            }
            else return retVal.Union(_supportedProperties).Distinct().ToList();
        }

        public override bool IsPropertySupported(object property)
        {
            //if (property.Equals(CacheProperties.Id))
            //    return _entryId != Guid.Empty;

            if (property.Equals(CacheProperties.SupportedProperties))
                return _supportedProperties != null;

            return CofeServices.PropertyDictionary.AttributeHelper.IsCachable(property) && base.IsPropertySupported(property);
        }

        public override bool IsPropertyReadOnly(object property)
        {
            //if (_supportedProperties.Contains(property))
            //    return false;
            //return base.IsPropertyReadOnly(property);

            return false;
        }

        public void ResetChanges()
        {
            lock (_changedProperties)
                _changedProperties.Clear();
        }

        public void ResetCache(Func<object, bool> filter = null)
        {
            ResetChanges();

            _supportedProperties = null;
            lock (_propertyValueDic)
                foreach (var prop in _propertyValueDic.Keys.ToArray())
                    switch ((int)prop)
                    {
                        case (int)CofeProperties.FullParseName:
                        case (int)CofeProperties.ParentParseName:
                        case (int)CacheProperties.Id:
                        case (int)CacheProperties.ParentId:
                        case (int)CofeEntryTypeProperties.EntryTypeInfo:
                        case (int)CofeProperties.DefaultInterfaceType:
                        case (int)CofeProperties.DefaultInterfaceTypeString:
                        case (int)CofeProperties.Type:
                            break; //These cannot be resetted.
                        default :
                            if (filter == null || filter(prop))
                            {
                                object value;
                                if (_propertyValueDic.ContainsKey(prop))
                                    _propertyValueDic.TryRemove(prop, out value);
                                unregisterProperties(prop);
                            }
                            break;
                    }
        }

        public bool CanCache(object property)
        {
            //var defType = DecoratorExtension.GetDefaultType(property);
            //if (defType.Equals(typeof(BitmapContainer)) || defType.Equals(typeof(StreamContainer)))
            //    return false;

            if (!(CofeServices.PropertyDictionary.AttributeHelper.IsCachable(property)))
                return false;

            return !(DecoratorExtension.IsAction(property));
        }


        private bool updatePropertyToCache(PropertyPair[] propertyPairs)
        {
            foreach (var pp in propertyPairs)
                updatePropertyToCache(pp);
                    
            return false;
        }

        private bool updatePropertyToCache(PropertyPair propertyPair)
        {
            _propertyValueDic[propertyPair.Property] = propertyPair.Value;

            switch ((int)propertyPair.Property)
            {
                case (int)CacheProperties.SupportedProperties:                    
                    _supportedProperties =
                        propertyPair.ValueAs<object[]>().Select(pId => CofeServices.PropertyDictionary.GetProperty((int)pId)).ToList();                        
                        //new List<object>(propertyPair.ValueAs<object[]>());
                    break;
                //case (int)CacheProperties.Id:

                default :
                    if (CanCache(propertyPair.Property))
                    {
                        
                        registerProperties(propertyPair.Property, () => _propertyValueDic[propertyPair.Property]);
                        _lastUpdateTime = DateTime.UtcNow;
                        //return true;
                    }
                    break;
            }

          

            if (propertyPair.Property.Equals(CacheProperties.SupportedDefinitions) || propertyPair.Property.Equals(CacheProperties.SupportedProperties))
                if (_propertyValueDic.ContainsKey(CacheProperties.SupportedDefinitions) && _propertyValueDic.ContainsKey(CacheProperties.SupportedProperties))
            {
                _supportedDefinitions = CofeServices.PropertyDictionary.CreatePropertyDefinitions(
                        base.SupportedDefinitions.GetSupportedProperties(),
                        (IPropertyDefinitions[])_propertyValueDic[CacheProperties.SupportedDefinitions]);
            }

            return false;

        }

        public Task<bool> CachePropertyAsync(PropertyPair[] propertyPair)
        {
            return Task<bool>.FromResult(updatePropertyToCache(propertyPair));
        }

        //public void ImportProperties(params IPropertyCache[] propertyCaches)
        //{
        //    foreach (var pc in propertyCaches)
        //        if (!pc.Equals(this))
        //            foreach (var prop in propertyCaches.GetSupportedProperties())
        //                if (pc.IsPropertySupported(prop) && _propertyValueDic.ContainsKey(prop))
        //                    _propertyValueDic[prop] = pc.GetOneProperty(prop);

        //}

        public override Task<bool> SetPropertyAsync(PropertyPair propertyPair)
        {
            if (CanCache(propertyPair.Property))
            {
                bool retVal = updatePropertyToCache(propertyPair);

                lock (_changedProperties)
                    if (!(_changedProperties.Contains(propertyPair.Property)))
                        _changedProperties.Add(propertyPair.Property);

                _lastUpdateTime = DateTime.UtcNow;
            }

            return Task.FromResult(false);
        }

        public Task WriteToDataStore()
        {
            return Task.Run(() => { });
        }

        public Task ReadFromDataStore()
        {
            return Task.Run(() => { });
        }

        public override string ToString()
        {
            return String.Format("MemoryPropertyCache : {0} properties", _propertyValueDic.Keys.Count());
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        #endregion

        #region Data

        private Guid _entryId;
        private ConcurrentDictionary<object, object> _propertyValueDic = new ConcurrentDictionary<object, object>();        
        private List<object> _changedProperties = new List<object>();
        private DateTime _lastUpdateTime = DateTime.MinValue;
        private List<object> _supportedProperties = null;
        private IPropertyDefinitions[] _supportedDefinitions = null;        

        #endregion

        #region Public Properties

        public List<object> ChangedProperties { get { return _changedProperties; } }
        public List<object> SupportedProperties { get { return _propertyValueDic.Keys.ToList(); } }

        public override IPropertyDefinitions[] SupportedDefinitions
        {
            get
            {
                if (_supportedDefinitions != null)
                    return _supportedDefinitions;
                else return base.SupportedDefinitions;
            }
        }


        #endregion









    }
}
